#pragma once
#include <ISceneNodeAnimator.h>
#include <ICursorControl.h>
#include <ISceneNode.h>
#include <ICameraSceneNode.h>
#include <ISceneManager.h>
#include <SViewFrustum.h>

namespace catan
{
	using namespace irr::scene;

	class CatanCameraAnimator : public scene::ISceneNodeAnimator
	{
	public:
		CatanCameraAnimator(gui::ICursorControl* cursor, f32 rotate, f32 zoom, f32 translate)
			: CursorControl(cursor), Zooming(false), Rotating(false), Moving(false),
			Translating(false), ZoomSpeed(zoom), RotateSpeed(rotate), TranslateSpeed(translate),
			CurrentZoom(150.0f), RotX(90.0f), RotY(45.0f), OldCamera(0), MousePos(0.5f, 0.5f)
		{
#ifdef _DEBUG
			setDebugName("CatanCameraAnimator");
#endif
			if (CursorControl)
			{
				CursorControl->grab();
				MousePos = CursorControl->getRelativePosition();
			}
			OldCamera = 0;
			OldTarget = core::vector3df();
			allKeysUp();
			mWheel = 0.0f;
		}


		//! destructor
		virtual ~CatanCameraAnimator()
		{
			if (CursorControl)
				CursorControl->drop();
		}


		//! It is possible to send mouse and key events to the camera. Most cameras
		//! may ignore this input, but camera scene nodes which are created for
		//! example with scene::ISceneManager::addMayaCameraSceneNode or
		//! scene::ISceneManager::addMeshViewerCameraSceneNode, may want to get this input
		//! for changing their position, look at target or whatever.
		virtual bool OnEvent(const SEvent& event)
		{
			if (event.EventType != EET_MOUSE_INPUT_EVENT)
				return false;

			switch(event.MouseInput.Event)
			{
			case EMIE_LMOUSE_PRESSED_DOWN:
				MouseKeys[0] = true;
				break;
			case EMIE_RMOUSE_PRESSED_DOWN:
				MouseKeys[2] = true;
				break;
			case EMIE_MMOUSE_PRESSED_DOWN:
				MouseKeys[1] = true;
				break;
			case EMIE_LMOUSE_LEFT_UP:
				MouseKeys[0] = false;
				break;
			case EMIE_RMOUSE_LEFT_UP:
				MouseKeys[2] = false;
				break;
			case EMIE_MMOUSE_LEFT_UP:
				MouseKeys[1] = false;
				break;
			case EMIE_MOUSE_MOVED:
				MousePos = CursorControl->getRelativePosition();
				break;
			case EMIE_MOUSE_WHEEL:
				mWheel = event.MouseInput.Wheel;
				break;
			case EMIE_RMOUSE_DOUBLE_CLICK:
				OldCamera = 0;
				OldTarget = core::vector3df();
				allKeysUp();
				mWheel = 0.0f;
				break;
			case EMIE_LMOUSE_DOUBLE_CLICK:
			case EMIE_MMOUSE_DOUBLE_CLICK:
			case EMIE_LMOUSE_TRIPLE_CLICK:
			case EMIE_RMOUSE_TRIPLE_CLICK:
			case EMIE_MMOUSE_TRIPLE_CLICK:
			case EMIE_COUNT:
				return false;
			}
			return false;
		}


		//! OnAnimate() is called just before rendering the whole scene.
		//! nodes may calculate or store animations here, and may do other useful things,
		//! dependent on what they are.
		void animateNode(ISceneNode *node, u32 timeMs)
		{
			//Alt + LM = Rotate around camera pivot
			//Alt + LM + MM = Dolly forth/back in view direction (speed % distance camera pivot - max distance to pivot)
			//Alt + MM = Move on camera plane (Screen center is about the mouse pointer, depending on move speed)

			if (!node || node->getType() != ESNT_CAMERA)
				return;

			ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);

			// If the camera isn't the active camera, and receiving input, then don't process it.
			if(!camera->isInputReceiverEnabled())
				return;

			scene::ISceneManager * smgr = camera->getSceneManager();
			if(smgr && smgr->getActiveCamera() != camera)
				return;

			if (OldCamera != camera)
			{
				OldTarget = camera->getTarget();
				OldCamera = camera;
				LastCameraTarget = OldTarget;
			}
			else
			{
				OldTarget += camera->getTarget() - LastCameraTarget;
			}

			core::vector3df target = camera->getTarget();

			f32 nRotX = RotX;
			f32 nRotY = RotY;
			f32 nZoom = CurrentZoom;

			if (mWheel != 0)
			{
				CurrentZoom -= mWheel * ZoomSpeed / 100;
				mWheel = 0.0f;
			}
			if ( (isMouseKeyDown(0) && isMouseKeyDown(2)) || isMouseKeyDown(1) )
			{
				if (!Zooming)
				{
					ZoomStart = MousePos;
					Zooming = true;
					nZoom = CurrentZoom;
				}
				else
				{
					const f32 targetMinDistance = 0.1f;
					nZoom += (ZoomStart.X - MousePos.X) * ZoomSpeed;

					if (nZoom < targetMinDistance) // jox: fixed bug: bounce back when zooming to close
						nZoom = targetMinDistance;
				}
			}
			else if (Zooming)
			{
				const f32 old = CurrentZoom;
				CurrentZoom = CurrentZoom + (ZoomStart.X - MousePos.X ) * ZoomSpeed;
				nZoom = CurrentZoom;

				if (nZoom < 0)
					nZoom = CurrentZoom = old;
				Zooming = false;
			}

			// Translation ---------------------------------

			core::vector3df translate(OldTarget), upVector(camera->getUpVector());

			core::vector3df tvectX = Pos - target;
			tvectX = tvectX.crossProduct(upVector);
			tvectX.normalize();

			const SViewFrustum* const va = camera->getViewFrustum();
			core::vector3df tvectY = (va->getFarLeftDown() - va->getFarRightDown());
			tvectY = tvectY.crossProduct(upVector.Y > 0 ? Pos - target : target - Pos);
			tvectY.normalize();

// 			if (isMouseKeyDown(0) && !Zooming)
// 			{
// 				if (!Translating)
// 				{
// 					TranslateStart = MousePos;
// 					Translating = true;
// 				}
// 				else
// 				{
// 					translate +=  tvectX * (TranslateStart.X - MousePos.X)*TranslateSpeed +
// 						tvectY * (TranslateStart.Y - MousePos.Y)*TranslateSpeed;
// 				}
// 			}
// 			else 
			if (Translating)
			{
				translate += tvectX * (TranslateStart.X - MousePos.X)*TranslateSpeed +
					tvectY * (TranslateStart.Y - MousePos.Y)*TranslateSpeed;
				OldTarget = translate;
				Translating = false;
			}

			// Rotation ------------------------------------

			if (isMouseKeyDown(2) && !Zooming)
			{
				if (!Rotating)
				{
					RotateStart = MousePos;
					Rotating = true;
					nRotX = RotX;
					nRotY = RotY;
				}
				else
				{
					nRotX += (RotateStart.X - MousePos.X) * RotateSpeed;
					nRotY += (RotateStart.Y - MousePos.Y) * RotateSpeed;
				}
			}
			else if (Rotating)
			{
				RotX += (RotateStart.X - MousePos.X) * RotateSpeed;
				RotY += (RotateStart.Y - MousePos.Y) * RotateSpeed;
				nRotX = RotX;
				nRotY = RotY;
				Rotating = false;
			}

			// Set Pos ------------------------------------

			target = translate;

			Pos.X = nZoom + target.X;
			Pos.Y = target.Y;
			Pos.Z = target.Z;

			Pos.rotateXYBy(nRotY, target);
			Pos.rotateXZBy(-nRotX, target);

			// Rotation Error ----------------------------

			// jox: fixed bug: jitter when rotating to the top and bottom of y
			upVector.set(0,1,0);
			upVector.rotateXYBy(-nRotY);
			upVector.rotateXZBy(-nRotX+180.f);

			camera->setPosition(Pos);
			camera->setTarget(target);
			camera->setUpVector(upVector);
			LastCameraTarget = camera->getTarget();
		}


		bool isMouseKeyDown(s32 key)
		{
			return MouseKeys[key];
		}


		void allKeysUp()
		{
			for (s32 i=0; i<3; ++i)
				MouseKeys[i] = false;
		}


		//! Sets the rotation speed
		void setRotateSpeed(f32 speed)
		{
			RotateSpeed = speed;
		}


		//! Sets the movement speed
		void setMoveSpeed(f32 speed)
		{
			TranslateSpeed = speed;
		}


		//! Sets the zoom speed
		void setZoomSpeed(f32 speed)
		{
			ZoomSpeed = speed;
		}


		//! Gets the rotation speed
		f32 getRotateSpeed() const
		{
			return RotateSpeed;
		}


		// Gets the movement speed
		f32 getMoveSpeed() const
		{
			return TranslateSpeed;
		}


		//! Gets the zoom speed
		f32 getZoomSpeed() const
		{
			return ZoomSpeed;
		}

		ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager)
		{
			return new CatanCameraAnimator(CursorControl, RotateSpeed, ZoomSpeed, TranslateSpeed);;
		}
		virtual bool isEventReceiverEnabled() const
		{
			return true;
		}

		//! Returns type of the scene node
		virtual ESCENE_NODE_ANIMATOR_TYPE getType() const 
		{
			return ESNAT_CAMERA_MAYA;
		}

		//! Creates a clone of this animator.
		/** Please note that you will have to drop
		(IReferenceCounted::drop()) the returned pointer after calling
		this. */

	private:

		bool MouseKeys[3];

		gui::ICursorControl *CursorControl;
		core::vector3df Pos;
		bool Zooming;
		bool Rotating;
		bool Moving;
		bool Translating;
		f32 mWheel;
		f32 ZoomSpeed;
		f32 RotateSpeed;
		f32 TranslateSpeed;
		core::position2df RotateStart;
		core::position2df ZoomStart;
		core::position2df TranslateStart;
		f32 CurrentZoom;
		f32 RotX, RotY;
		core::vector3df OldTarget;
		core::vector3df LastCameraTarget;	// to find out if the camera target was moved outside this animator
		scene::ICameraSceneNode* OldCamera;

		core::position2df MousePos;
	};
	
}